package example.runner;
import java.awt.Point;
import java.io.File;

import logging.LogSystem;
import logging.Relationship;
import logging.Themes;

import service.genetic.NetworkGeneticService;
import service.network.NetworkFilesystemService;
import visual.ValuePerTime;
import visual.VisualMatrix;



/**
 * Klasse, die alles koordiniert
 * 
 * Das Prinzip ist, dass die Runner genau wie die Fische im Beispiel von Sheepy
 * so viel Essen wie möglich zu sich nehmen. Als Input sehen sie je zwei Felder
 * in jede Richtung. Dabei ist es den Runnern nicht möglich Wände von anderen
 * Runnern unterscheiden, sie können nur zwischen leer,essen und versperrt
 * entscheiden.
 * 
 * xxxxx xxxxx xxrxx xxxxx xxxxx
 * 
 * Die Runner können in alle Richtungen laufen.
 * 
 * hier werden die Runner erstellt und die Evolution durchgeführt.
 * 
 * @author Ramox
 * 
 */
public class Game
{
	public static final int FIELD_HEIGHT = 10; // höhe des Feldes in kästchen
	public static final int FIELD_WIDTH = 10; // breite des feldes in kästchen
	public static final int RUNNER_COUNT = 4;
	public static final int ROUNDS_UNTIL_EVOLUTION = 300;
	public static int FOOD_COUNT = 400; // anzahl des essens auf dem Feld

	private FieldValues[][] _field;

	private Runner[] _runner;
	private ControlField _cf;
	private ValuePerTime _vpt;
	private VisualMatrix _vm;

	/**
	 * Erstellt das Feld und die erste Generation von Runner, sowie das erste
	 * Essen.
	 * 
	 * TODO:gui-Verknüpfung sollte mit Beobachter-Muster realisiert werden
	 * 
	 * @param cf
	 *            gui-Komponente, damit sie aktualisiert wird
	 */
	public Game(ControlField cf)
	{
		_cf = cf;
		_vpt = new ValuePerTime();
		_vm = new VisualMatrix();
		_vm.setVisible(true);
		_vpt.setVisible(true);
		createField();
		createRunners();
		createFood();
	}
	
	/**
	 * erstellt das Essen und platziert es
	 */
	private void createFood()
	{
		for (int i = 0; i < FOOD_COUNT; i++)
		{
			newFood();
		}
	}

	/**
	 * erstellt ein neues Essen, das zufällig platziert wird.
	 */
	private void newFood()
	{
		int x, y;
		do
		{
			x = (int) (Math.random() * FIELD_WIDTH);
			y = (int) (Math.random() * FIELD_HEIGHT);

			if (_field[x][y] == FieldValues.Frei)
			{
				_field[x][y] = FieldValues.Essen;
				break;
			}
		}
		while (true);
	}

	/**
	 * gibt das feld zurück
	 * 
	 * @return feld
	 */
	public FieldValues[][] getField()
	{
		return _field;
	}

	int coooo = 0;
	/**
	 * Startet das Spiel
	 * 
	 * Es gibt eine Endlosschleife, die immer wieder neue Generationen erzeugt.
	 * Ausgegeben wird die Nummer der Generation und die Fitness des besten
	 * runners.
	 * 
	 */
	int count = 0;
	public void startGame()
	{
	
		do
		{
			for (int i = 0; i < ROUNDS_UNTIL_EVOLUTION; i++)
			{
				moveRunners();
					_cf.repaint();
				
				
			}
			
			System.out.println("Generation: " + count);
			
			evolution();
			createField();
			for (int i = 0; i < RUNNER_COUNT; i++)
			{
				setRandomRunnerPosition(_runner[i]);
			}
			createFood();

			count++;
		}
		while (true); // TODO: endlosschleife!!!
	}

	/**
	 * Erstellt die neue Generation
	 */
	private void evolution()
	{
		// sortiert die runner nach ihrer fitness
		for (int j = 0; j < RUNNER_COUNT; j++)
		{
			for (int i = j; i < RUNNER_COUNT; i++)
			{
				if (_runner[i].getFitness() > _runner[j].getFitness())
				{
					Runner r = _runner[i];
					_runner[i] = _runner[j];
					_runner[j] = r;
				}
			}
		}
		//NetworkFilesystemService nfs = new NetworkFilesystemService();
		//nfs.saveNetwork(new File("/home/sheepy/network" + (int)((Math.random() * 1000000)) + ".net")
		//, _runner[0].getNet());
		// fitness des besten
		//System.out.println("Be.rsterFisch: " + _runner[0].getFitness());	
		NetworkGeneticService ngs = new NetworkGeneticService();
		_vpt.addData( (int) (_runner[0].getFitness()));
		_vm.updateGUI(_runner[0].getNet());

		Runner[] runner = new Runner[RUNNER_COUNT];
		runner[0] = new Runner(_runner[0].getNet());
		runner[1] = new Runner(_runner[1].getNet());
		runner[2] = new Runner(_runner[2].getNet());
		runner[3] = new Runner(_runner[3].getNet());
		
		
		for(int i = 4; i < RUNNER_COUNT;i++){
			int z1 = (int)(Math.random() * (RUNNER_COUNT/40));
			int z2 = (int)(Math.random() * (RUNNER_COUNT/20));
			runner[i] = new Runner(ngs.recombineNetworks(_runner[z1].getNet(), _runner[z2].getNet()));		
			ngs.mutateNetwork(runner[i].getNet(),0.0001 ,0.1);
			ngs.CrossOverNetwork(runner[i].getNet(), 0.0001, 10);
			//ngs.mutateNetworkRandom(runner[i].getNet(),0.0000001,1);
		}

		_runner = runner;
	}
	
	public double getRate(){
		double ret = (50.0/count);
		if( ret < Math.pow(10, -4)){
			ret = Math.pow(10, -4);
		}
		return ret;
	}

	/**
	 * bewegt alle Runner
	 */
	private void moveRunners()
	{
		askSpeed();
		for (int i = 0; i < RUNNER_COUNT; i++)
		{
			Point pos = getRunnerPosition(i);
			Direction dir = _runner[i].getNextDirection(getUmgebung(pos));
			moveRunner(i, dir);
			
		}
	}

	int speed_verz = 0;
	
	public void setSpeedVerz(int i){
		speed_verz = i;
	}
	private void askSpeed() {
		if(speed_verz > 0){
			try{
				synchronized (this) {
					this.wait(speed_verz);
				}
			}catch(Exception e){
				e.printStackTrace();
			}
		}
	}

	/**
	 * bewegt einen Runner mit der id id in die Richtung dir, wenn es möglich
	 * ist
	 * 
	 * @param id
	 *            id des zu bewegenden runner
	 * @param dir
	 *            Richtung der bewegung
	 */
	private void moveRunner(int id, Direction dir)
	{
		Point pos = getRunnerPosition(id);
		int x = pos.x;
		int y = pos.y;

		if (dir == Direction.NorthWest)
		{
			x -= 1;
			y -= 1;
		}
		if (dir == Direction.North)
		{
			y -= 1;
		}
		if (dir == Direction.NorthEast)
		{
			x += 1;
			y -= 1;
		}
		if (dir == Direction.East)
		{
			x += 1;
		}
		if (dir == Direction.SouthEast)
		{
			x += 1;
			y += 1;
		}
		if (dir == Direction.South)
		{
			y += 1;
		}
		if (dir == Direction.SouthWest)
		{
			x -= 1;
			y += 1;
		}
		if (dir == Direction.West)
		{
			x -= 1;
		}
		
		if (_field[x][y] == FieldValues.Frei) // leer
		{
			_field[pos.x][pos.y] = FieldValues.Frei;
			_field[x][y] = FieldValues.Runner;
			_runner[id].setPosition(x,y);
			_runner[id].increaseSteps();
		}
		else if (_field[x][y] == FieldValues.Essen) // essen
		{
			_field[pos.x][pos.y] = FieldValues.Frei;
			_field[x][y] = FieldValues.Runner;
			_runner[id].setPosition(x,y);
			_runner[id].increaseHealth();
			_runner[id].increaseSteps();
			newFood();
		}
		else //wand oder anderer Runner
		{
			_runner[id].decreaseLive();
		}

	}

	/**
	 * gibt die umgebung der position pos wieder.
	 * 
	 * xxxxx
	 * xxxxx
	 * xxpxx
	 * xxxxx
	 * xxxxx
	 * 
	 * @param pos Position, zu der die Umgebung gesucht ist
	 * @return Umgebung (5x5) um die position
	 */
	private FieldValues[] getUmgebung(Point pos)
	{
		FieldValues[] umgebung = new FieldValues[25];
		int counter=0;
		pos.x -= 5;
		pos.y -= 5;

		for (int i = 0; i < 5; i++)
		{
			for (int j = 0; j < 5; j++)
			{
				if ((pos.x + j >= 0) && (pos.y + i >= 0)
						&& (pos.x + j <= FIELD_WIDTH)
						&& pos.y + i <= FIELD_WIDTH)
				{
					umgebung[counter] = _field[pos.x + j][pos.y + i];
					counter++;
				}else{
					umgebung[counter] = FieldValues.Wand;
					counter++;
				}
			}
		}
		return umgebung;
	}

	/**
	 * holt die Position des Runners aus dem Feld
	 * 
	 * @param id id des Runners, von dem die position gesucht ist
	 * 
	 * @return Position des Runners
	 */
	private Point getRunnerPosition(int id)
	{
		return _runner[id].getPosition();
	}

	/**
	 * erzeugt die erste Generation von Runnern
	 */
	private void createRunners()
	{
		_runner = new Runner[RUNNER_COUNT];
		
		for (int i = 0; i < RUNNER_COUNT; i++)
		{	
			_runner[i] = new Runner(rn.getNet());
			setRandomRunnerPosition(_runner[i]);
		}
	}
	
	GutesNetz rn = new GutesNetz();

	/**
	 * Setzt den Runner mit der id (idPlus1-1) zufällig auf das feld
	 * 
	 * @param idPlus1 id des Runners + 1, weil die id auch 0 sein könnte
	 */
	private void setRandomRunnerPosition(Runner r)
	{
		int x;
		int y;

		do
		{
			x = (int) (Math.random() * FIELD_WIDTH);
			y = (int) (Math.random() * FIELD_HEIGHT);
		}
		while(_field[x][y] != FieldValues.Frei);
		
		_field[x][y] = FieldValues.Runner;
		r.setPosition(x,y);
		
	}

	/**
	 * erzeugt das Feld und die Wände
	 */
	private void createField()
	{
		_field = new FieldValues[FIELD_WIDTH][FIELD_HEIGHT];

		
		for(int i = 0; i < FIELD_WIDTH; i++){
			for(int j = 0; j < FIELD_WIDTH; j++){
				_field[i][j] = FieldValues.Frei;
			}				
		}
		
		// Wände
		for (int i = 0; i < FIELD_WIDTH; i++)
		{
			_field[i][0] = FieldValues.Wand;
			_field[i][FIELD_HEIGHT - 1] = FieldValues.Wand;
		}
		for (int i = 0; i < FIELD_HEIGHT; i++)
		{
			_field[0][i] = FieldValues.Wand;
			_field[FIELD_WIDTH - 1][i] = FieldValues.Wand;
		}
	}
}
